package fintecher.com.util;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.ChannelSftp.LsEntry;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Vector;

/**
 * SFTP 工具类
 *
 * @author Super Jens Wang
 * @version 1.0 @ 2016年7月20日 上午4:57:27
 */

@Component
public class SftpUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(SftpUtils.class);

    private final static String USERAUTH_GSSAPI_WITH_MIC = "userauth.gssapi-with-mic";
    private final static String NO = "no";
    private final static String STRICT_HOST_KEY_CHECKING = "StrictHostKeyChecking";
    private final static int CONNECT_TIMEOUT = 20000;
    private final static String CHANNEL_TYPE = "sftp";
    //private final static String ROOT_PATH = "/";
    private final static String DISCONNECT_ERROR_MSG = "关闭链接失败";
    private final static String DELET_FILE_ERROR_MSG = "删除文件[{}]失败";

    private ChannelSftp sftp;

    /**
     * 主机
     */
    @Value("${ftp_host}")
    private String host;

    /**
     * 端口
     */
    @Value("${ftp_port}")
    private int port;

    /**
     * 用户名
     */
    @Value("${ftp_user}")
    private String username;

    /**
     * 密码
     */
    @Value("${ftp_password}")
    private String password;

    /**
     * domain
     */
    @Value("${ftp_domain}")
    private String domain;

    /**
     * 根目录
     */
    @Value("${ftp_path}")
    private String rootPath;

//    public SftpUtils(String host, int port, String username, String password, String domain, String rootPath) {
//        this.host = host;
//        this.port = port;
//        this.username = username;
//        this.password = password;
//        this.domain = domain;
//        this.rootPath = rootPath;
//    }


    /**
     * 连接sftp服务器
     */
    public SftpUtils connect() throws Exception {
        JSch jsch = new JSch();
        Session sshSession = jsch.getSession(this.username, this.host, this.port);
        sshSession.setPassword(password);
        Properties sshConfig = new Properties();
        sshConfig.put(USERAUTH_GSSAPI_WITH_MIC, NO);
        sshConfig.put(STRICT_HOST_KEY_CHECKING, NO);
        sshSession.setConfig(sshConfig);
        sshSession.connect(CONNECT_TIMEOUT);
        Channel channel = sshSession.openChannel(CHANNEL_TYPE);
        sftp = (ChannelSftp) channel;
        if (!sftp.isConnected()) {
            sftp.connect(CONNECT_TIMEOUT);
        }
        return this;
    }

    /**
     * 断开
     */
    public void disconnect() {
        if (null != sftp && sftp.isConnected()) {
            try {
                sftp.getSession().disconnect();
            } catch (Exception e) {
                // igonore
            }
            sftp.disconnect();
            sftp = null;
        }
    }

    // TODO 断开，并删除本地文件
    // public void disconnect(List<String> files) {
    // if (null != sftp && sftp.isConnected()) {
    // sftp.disconnect();
    // }
    // }

    /**
     * 断开，并删除本地文件
     */
    public void disconnect(List<File> files) {
        try {
            if (null != sftp && sftp.isConnected()) {
                sftp.disconnect();
            }
        } catch (Throwable e) {
            LOGGER.error(DISCONNECT_ERROR_MSG, e);
        }
        // 删除文件
        for (File file : files) {
            try {
                if (!file.exists() || !file.delete()) {
                    LOGGER.info(DELET_FILE_ERROR_MSG, ToStringBuilder.reflectionToString(file));
                }
            } catch (Throwable e) {
                LOGGER.error(DELET_FILE_ERROR_MSG, ToStringBuilder.reflectionToString(file), e);
            }
        }
    }

    /**
     * 上传单个文件
     *
     * @param directory  上传的目录
     * @param uploadFile 要上传的文件
     */
    public SftpUtils upload(String directory, String uploadFile) throws Exception {
        File file = new File(uploadFile);
        upload(directory, new FileInputStream(file), file.getName());
        return this;
    }

    /**
     * 上传单个文件
     *
     * @param directory   上传的目录
     * @param inputStream 要上传的文件输入流
     * @param fileName    文件名
     */
    public SftpUtils upload(String directory, InputStream inputStream, String fileName) throws Exception {
        try {
            cd(directory);
        } catch (Exception e) {
            LOGGER.error("进入目录 [{}] 失败", directory, e);
            String paths[] = StringUtils.split(directory, File.separatorChar);
            sftp.cd(rootPath);
            for (String path : paths) {
                try {
                    cd(path);
                } catch (Throwable ef) {
                    LOGGER.error("目录 [{}] 不存在，开始创建", path, ef);
                    sftp.mkdir(path);
                    cd(path);
                }
            }
        }
        sftp.put(inputStream, fileName);
        return this;
    }

    /**
     * 下载单个文件
     *
     * @param directory     下载目录
     * @param downloadFile  下载的文件
     * @param saveDirectory 存在本地的路径
     */
    public SftpUtils download(String directory, String downloadFile, String saveDirectory) throws Exception {
        String saveFile = saveDirectory + File.separator + downloadFile;
        cd(directory);
        File file = new File(saveFile);
        sftp.get(downloadFile, new FileOutputStream(file));
        return this;
    }

    /**
     * 下载目录下全部文件
     *
     * @param directory     下载目录
     * @param saveDirectory 存在本地的路径
     */
    public SftpUtils downloadByDirectory(String directory, String saveDirectory) throws Exception {
        List<String> downloadFileList = listFiles(directory);
        for (String downloadFile : downloadFileList) {
            if (downloadFile.contains(".") && !downloadFile.startsWith(".")) {
                download(directory, downloadFile, saveDirectory);
            }
        }
        return this;
    }

    /**
     * 删除文件
     *
     * @param directory  - 要删除文件所在目录
     * @param deleteFile - 要删除的文件
     */
    public SftpUtils delete(String directory, String deleteFile) throws Exception {
        return cd(directory).rm(deleteFile);
    }

    /**
     * 删除文件
     */
    public SftpUtils rm(String path) throws Exception {
        sftp.rm(path);
        return this;
    }

    /**
     * 删除文件
     */
    public SftpUtils rmdir(String path) throws Exception {
        sftp.rmdir(path);
        return this;
    }

    /**
     * 列出目录下的文件
     *
     * @param directory - 要列出的目录
     * @return list - 文件名列表
     */
    public List<String> listFiles(String directory) throws Exception {
        List<String> fileNameList = new ArrayList<String>();
        Vector<?> fileList = sftp.ls(directory);
        for (Object file : fileList) {
            String fileName = ((LsEntry) file).getFilename();
            if (StringUtils.equals(".", fileName) || StringUtils.equals("..", fileName)) {
                continue;
            }
            fileNameList.add(fileName);
        }

        return fileNameList;
    }

    /**
     * 更改文件名
     *
     * @param directory - 文件所在目录
     * @param oldFileNm - 原文件名
     * @param newFileNm - 新文件名
     */
    public SftpUtils rename(String directory, String oldFileNm, String newFileNm) throws Exception {
        sftp.cd(directory);
        sftp.rename(oldFileNm, newFileNm);
        return this;
    }

    /**
     * 进入目录
     */
    public SftpUtils cd(String directory) throws Exception {
        sftp.cd(directory);
        return this;
    }

    /**
     * 获取输入流
     */
    public InputStream getInputStream(String src) throws Exception {
        return sftp.get(src);
    }

    public String getDomain() {
        return domain;
    }

    public void setDomain(String domain) {
        this.domain = domain;
    }

    public String getRootPath() {
        return rootPath;
    }

    public void setRootPath(String rootPath) {
        this.rootPath = rootPath;
    }
}
